/* Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */

#include "sql_priv.h"
#include "unireg.h"
#include "sp_pcontext.h"
#include "sp_head.h"


bool sp_condition_value::equals(const sp_condition_value *cv) const
{
    DBUG_ASSERT(cv);

    if (this == cv)
        return true;

    if (type != cv->type)
        return false;

    switch (type) {
    case sp_condition_value::ERROR_CODE:
        return (mysqlerr == cv->mysqlerr);

    case sp_condition_value::SQLSTATE:
        return (strcmp(sql_state, cv->sql_state) == 0);

    default:
        return true;
    }
}


void sp_pcontext::init(uint var_offset,
                       uint cursor_offset,
                       int num_case_expressions)
{
    m_var_offset = var_offset;
    m_cursor_offset = cursor_offset;
    m_num_case_exprs = num_case_expressions;
    m_labels.empty();
}


sp_pcontext::sp_pcontext()
    : Sql_alloc(),
      m_level(0),
      m_max_var_index(0), m_max_cursor_index(0),
      m_parent(NULL), m_pboundary(0),
      m_scope(REGULAR_SCOPE)
{
    init(0, 0, 0);
}


sp_pcontext::sp_pcontext(sp_pcontext *prev, sp_pcontext::enum_scope scope)
    : Sql_alloc(),
      m_level(prev->m_level + 1),
      m_max_var_index(0), m_max_cursor_index(0),
      m_parent(prev), m_pboundary(0),
      m_scope(scope)
{
    init(prev->m_var_offset + prev->m_max_var_index,
         prev->current_cursor_count(),
         prev->get_num_case_exprs());
}


sp_pcontext::~sp_pcontext()
{
    for (int i = 0; i < m_children.elements(); ++i)
        delete m_children.at(i);
}


sp_pcontext *sp_pcontext::push_context(THD *thd, sp_pcontext::enum_scope scope)
{
    sp_pcontext *child = new (thd->mem_root) sp_pcontext(this, scope);

    if (child)
        m_children.append(child);

    return child;
}


sp_pcontext *sp_pcontext::pop_context()
{
    m_parent->m_max_var_index += m_max_var_index;
    uint submax = max_cursor_index();

    if (submax > m_parent->m_max_cursor_index)
        m_parent->m_max_cursor_index = submax;

    if (m_num_case_exprs > m_parent->m_num_case_exprs)
        m_parent->m_num_case_exprs = m_num_case_exprs;

    return m_parent;
}


uint sp_pcontext::diff_handlers(const sp_pcontext *ctx, bool exclusive) const
{
    uint n = 0;
    const sp_pcontext *pctx = this;
    const sp_pcontext *last_ctx = NULL;

    while (pctx && pctx != ctx) {
        n += pctx->m_handlers.elements();
        last_ctx = pctx;
        pctx = pctx->parent_context();
    }

    if (pctx)
        return (exclusive && last_ctx ? n - last_ctx->m_handlers.elements() : n);

    return 0;			// Didn't find ctx
}


uint sp_pcontext::diff_cursors(const sp_pcontext *ctx, bool exclusive) const
{
    uint n = 0;
    const sp_pcontext *pctx = this;
    const sp_pcontext *last_ctx = NULL;

    while (pctx && pctx != ctx) {
        n += pctx->m_cursors.elements();
        last_ctx = pctx;
        pctx = pctx->parent_context();
    }

    if (pctx)
        return  (exclusive && last_ctx ? n - last_ctx->m_cursors.elements() : n);

    return 0;			// Didn't find ctx
}


sp_variable *sp_pcontext::find_variable(LEX_STRING name,
                                        bool current_scope_only) const
{
    uint i = m_vars.elements() - m_pboundary;

    while (i--) {
        sp_variable *p = m_vars.at(i);

        if (my_strnncoll(system_charset_info,
                         (const uchar *)name.str, name.length,
                         (const uchar *)p->name.str, p->name.length) == 0)
            return p;
    }

    return (!current_scope_only && m_parent) ?
           m_parent->find_variable(name, false) :
           NULL;
}


sp_variable *sp_pcontext::find_variable(uint offset) const
{
    if (m_var_offset <= offset && offset < m_var_offset + m_vars.elements())
        return m_vars.at(offset - m_var_offset);  // This frame

    return m_parent ?
           m_parent->find_variable(offset) :    // Some previous frame
           NULL;                                // Index out of bounds
}


sp_variable *sp_pcontext::add_variable(THD *thd,
                                       LEX_STRING name,
                                       enum enum_field_types type,
                                       sp_variable::enum_mode mode)
{
    sp_variable *p =
        new (thd->mem_root) sp_variable(name, type, mode, current_var_count());

    if (!p)
        return NULL;

    ++m_max_var_index;
    return m_vars.append(p) ? NULL : p;
}


sp_label *sp_pcontext::push_label(THD *thd, LEX_STRING name, uint ip)
{
    sp_label *label =
        new (thd->mem_root) sp_label(name, ip, sp_label::IMPLICIT, this);

    if (!label)
        return NULL;

    m_labels.push_front(label);
    return label;
}


sp_label *sp_pcontext::find_label(LEX_STRING name)
{
    List_iterator_fast<sp_label> li(m_labels);
    sp_label *lab;

    while ((lab = li++)) {
        if (my_strcasecmp(system_charset_info, name.str, lab->name.str) == 0)
            return lab;
    }

    /*
      Note about exception handlers.
      See SQL:2003 SQL/PSM (ISO/IEC 9075-4:2003),
      section 13.1 <compound statement>,
      syntax rule 4.
      In short, a DECLARE HANDLER block can not refer
      to labels from the parent context, as they are out of scope.
    */
    return (m_parent && (m_scope == REGULAR_SCOPE)) ?
           m_parent->find_label(name) :
           NULL;
}


bool sp_pcontext::add_condition(THD *thd,
                                LEX_STRING name,
                                sp_condition_value *value)
{
    sp_condition *p = new (thd->mem_root) sp_condition(name, value);

    if (p == NULL)
        return true;

    return m_conditions.append(p);
}


sp_condition_value *sp_pcontext::find_condition(LEX_STRING name,
        bool current_scope_only) const
{
    uint i = m_conditions.elements();

    while (i--) {
        sp_condition *p = m_conditions.at(i);

        if (my_strnncoll(system_charset_info,
                         (const uchar *) name.str, name.length,
                         (const uchar *) p->name.str, p->name.length) == 0)
            return p->value;
    }

    return (!current_scope_only && m_parent) ?
           m_parent->find_condition(name, false) :
           NULL;
}


sp_handler *sp_pcontext::add_handler(THD *thd,
                                     sp_handler::enum_type type)
{
    sp_handler *h = new (thd->mem_root) sp_handler(type, this);

    if (!h)
        return NULL;

    return m_handlers.append(h) ? NULL : h;
}


bool sp_pcontext::check_duplicate_handler(
    const sp_condition_value *cond_value) const
{
    for (int i = 0; i < m_handlers.elements(); ++i) {
        sp_handler *h = m_handlers.at(i);
        List_iterator_fast<sp_condition_value> li(h->condition_values);
        sp_condition_value *cv;

        while ((cv = li++)) {
            if (cond_value->equals(cv))
                return true;
        }
    }

    return false;
}


sp_handler *sp_pcontext::find_handler(const char *sql_state,
                                      uint sql_errno,
                                      Sql_condition::enum_warning_level level) const
{
    sp_handler *found_handler = NULL;
    sp_condition_value *found_cv = NULL;

    for (int i = 0; i < m_handlers.elements(); ++i) {
        sp_handler *h = m_handlers.at(i);
        List_iterator_fast<sp_condition_value> li(h->condition_values);
        sp_condition_value *cv;

        while ((cv = li++)) {
            switch (cv->type) {
            case sp_condition_value::ERROR_CODE:
                if (sql_errno == cv->mysqlerr &&
                        (!found_cv ||
                         found_cv->type > sp_condition_value::ERROR_CODE)) {
                    found_cv = cv;
                    found_handler = h;
                }

                break;

            case sp_condition_value::SQLSTATE:
                if (strcmp(sql_state, cv->sql_state) == 0 &&
                        (!found_cv ||
                         found_cv->type > sp_condition_value::SQLSTATE)) {
                    found_cv = cv;
                    found_handler = h;
                }

                break;

            case sp_condition_value::WARNING:
                if ((is_sqlstate_warning(sql_state) ||
                        level == Sql_condition::WARN_LEVEL_WARN) && !found_cv) {
                    found_cv = cv;
                    found_handler = h;
                }

                break;

            case sp_condition_value::NOT_FOUND:
                if (is_sqlstate_not_found(sql_state) && !found_cv) {
                    found_cv = cv;
                    found_handler = h;
                }

                break;

            case sp_condition_value::EXCEPTION:
                if (is_sqlstate_exception(sql_state) &&
                        level == Sql_condition::WARN_LEVEL_ERROR && !found_cv) {
                    found_cv = cv;
                    found_handler = h;
                }

                break;
            }
        }
    }

    if (found_handler)
        return found_handler;

    // There is no appropriate handler in this parsing context. We need to look up
    // in parent contexts. There might be two cases here:
    //
    // 1. The current context has REGULAR_SCOPE. That means, it's a simple
    // BEGIN..END block:
    //     ...
    //     BEGIN
    //       ... # We're here.
    //     END
    //     ...
    // In this case we simply call find_handler() on parent's context recursively.
    //
    // 2. The current context has HANDLER_SCOPE. That means, we're inside an
    // SQL-handler block:
    //   ...
    //   DECLARE ... HANDLER FOR ...
    //   BEGIN
    //     ... # We're here.
    //   END
    //   ...
    // In this case we can not just call parent's find_handler(), because
    // parent's handler don't catch conditions from this scope. Instead, we should
    // try to find first parent context (we might have nested handler
    // declarations), which has REGULAR_SCOPE (i.e. which is regular BEGIN..END
    // block).
    const sp_pcontext *p = this;

    while (p && p->m_scope == HANDLER_SCOPE)
        p = p->m_parent;

    if (!p || !p->m_parent)
        return NULL;

    return p->m_parent->find_handler(sql_state, sql_errno, level);
}


bool sp_pcontext::add_cursor(LEX_STRING name)
{
    if (m_cursors.elements() == (int) m_max_cursor_index)
        ++m_max_cursor_index;

    return m_cursors.append(name);
}


bool sp_pcontext::find_cursor(LEX_STRING name,
                              uint *poff,
                              bool current_scope_only) const
{
    uint i = m_cursors.elements();

    while (i--) {
        LEX_STRING n = m_cursors.at(i);

        if (my_strnncoll(system_charset_info,
                         (const uchar *) name.str, name.length,
                         (const uchar *) n.str, n.length) == 0) {
            *poff = m_cursor_offset + i;
            return true;
        }
    }

    return (!current_scope_only && m_parent) ?
           m_parent->find_cursor(name, poff, false) :
           false;
}


void sp_pcontext::retrieve_field_definitions(
    List<Create_field> *field_def_lst) const
{
    /* Put local/context fields in the result list. */
    for (int i = 0; i < m_vars.elements(); ++i) {
        sp_variable *var_def = m_vars.at(i);
        field_def_lst->push_back(&var_def->field_def);
    }

    /* Put the fields of the enclosed contexts in the result list. */

    for (int i = 0; i < m_children.elements(); ++i)
        m_children.at(i)->retrieve_field_definitions(field_def_lst);
}


const LEX_STRING *sp_pcontext::find_cursor(uint offset) const
{
    if (m_cursor_offset <= offset &&
            offset < m_cursor_offset + m_cursors.elements()) {
        return &m_cursors.at(offset - m_cursor_offset);   // This frame
    }

    return m_parent ?
           m_parent->find_cursor(offset) :  // Some previous frame
           NULL;                            // Index out of bounds
}
